/** Application to test the basics of the boost python system.
 *
 * Used for exploring how all this stuff works.
 */

#include <boost/python.hpp>
#include <boost/python/extract.hpp>
#include <boost/python/converter/registry.hpp>

#include <iostream>

#include <boost/shared_ptr.hpp>

#include <Hello.h>

namespace bp = boost::python;

class TestClass
{
public:
   TestClass()
      : mIntVal(12), mFloatVal(12.21f)
   {;}

   void printValues()
   {
      std::cout << " Intval: " << mIntVal << "  Floatval:" << mFloatVal << std::endl;
   }

   int getIntVal()
   { return mIntVal; }
   void setIntVal(int val)
   { mIntVal = val; }

   float getFloatVal()
   { return mFloatVal; }

   void setValueFrom(TestClass& other)
   {
      mIntVal = other.mIntVal;
      mFloatVal = other.mFloatVal;
   }


public:
   int      mIntVal;
   float    mFloatVal;
};


int main(int argc, char* argv[])
{
   int ret_val;

   // ---- Initialization ------ //
   // Init interpreter
   Py_Initialize();

   std::cout << "Creating wrapper for TestClass." << std::endl;
   // Create a wrapper for the TestClass

   /*
   bp::object TestClassWrapper;

   TestClassWrapper = bp::class_<TestClass>("TestClass", bp::init<>())
                                 .def("printValues", &TestClass::printValues);
   */

   bp::class_<TestClass> TestClassWrapper = bp::class_<TestClass>("TestClass", bp::init<>());
   TestClassWrapper.def("printValues", &TestClass::printValues);
   TestClassWrapper.def("getFloatVal", &TestClass::getFloatVal);
   TestClassWrapper.def("setValueFrom", &TestClass::setValueFrom);

   // Instantiate it.
   std::cout << "Instantiating test_class." << std::endl;
   bp::object test_class( TestClassWrapper() );
   TestClass test_class2;
   test_class2.setIntVal(13);

   std::cout << "Outputting test_class:\n";
   test_class.attr("printValues")();

   std::cout << "Setting then outputting again.\n";
   test_class.attr("setValueFrom")(test_class2);
   test_class.attr("printValues")();

   // Test object interface
   bp::object test_int_obj(5);
   bp::object test_str_obj("My, string, here");
   //bp::list split_list = test_str_obj.split(',');

   int my_int = bp::extract<int>(test_int_obj);

   std::cout << "Running dir() in initial env\n";
   ret_val = PyRun_SimpleString("print dir()");

   // Retrieve the default main module and namespace
   bp::handle<> main_module( bp::borrowed(PyImport_AddModule("__main__")));
   bp::dict main_namespace;
   main_namespace = bp::dict(
                        bp::handle<>(
                                 bp::borrowed(
                                    PyModule_GetDict(main_module.get()) ) ) );

   std::cout << "Running dir on the acquired main namespace.\n";
   try
   {
      bp::handle<> r1(
         PyRun_String("import sys;     \n"
                      "import os;     \n"
                      "print ' cwd: ', os.getcwd();   \n"
                      "sys.path.append(os.getcwd());  \n"
                      "print 'path: ', sys.path;   \n"
                      "print dir();    \n",
                      Py_file_input,
                      main_namespace.ptr(), main_namespace.ptr())
      );


      bp::handle<> r2(
         PyRun_String("print 'importing hello...'  \n"
                      "import hello                \n"
                      "import time                 \n",
                      Py_file_input,
                      main_namespace.ptr(), main_namespace.ptr())
      );

   //bp::str hello_mod_name("hello");
   //bp::handle<> hello_mod( PyImport_Import(hello_mod_name.ptr()) );
   //bp::handle<> hello_mod( PyImport_ImportModule("hello"));

   //bp::handle<> hello_mod( PyImport_ImportModuleEx(hello_mod_name, main_namespace.ptr(), main_namespace.ptr(), NULL ));

      std::cout << "C++: Running dir on the main namespace.\n";
      bp::handle<> r3(
         PyRun_String("print dir()     \n",
                      Py_file_input,
                       main_namespace.ptr(), main_namespace.ptr())
      );

      // Print the symbols in the main namespace


      // Get the hello class reference and then instantiate one
      std::cout << "Creating Hello object and using:\n";
      bp::object HelloClass = main_namespace["hello"].attr("Hello");
      bp::object hello_obj = HelloClass();
      hello_obj.attr("printWelcome")();
      hello_obj.attr("setMessage")("New message");
      hello_obj.attr("printWelcome")();

      bp::object GreeterClass = main_namespace["hello"].attr("Greeter");
      // --- Shared ptr tests --- //
      // To test:
      // - Create C++ obj, pass as shared_ptr to python created class
      {
         std::cout << "Creating C++ obj, passing as shared_ptr to python created class.\n";
         bp::object greeter_obj = GreeterClass();
         greeter_obj.attr("greet")();
         boost::shared_ptr<Hello> my_hello(new Hello);
         my_hello->setName("Cpp Hello");
         greeter_obj.attr("setHello")(my_hello);
         greeter_obj.attr("greet")();
      }

      // - Create C++ obj, pass as shared_ptr to C++ created class
      {
         std::cout << " Create C++ obj, pass as shared_ptr to C++ created class.\n";
         boost::shared_ptr<Greeter> my_greeter(new Greeter());
         main_namespace["cpp_greeter_obj"] = bp::object(my_greeter);
         bp::object greeter_obj = main_namespace["cpp_greeter_obj"];
         greeter_obj.attr("greet")();
         boost::shared_ptr<Hello> my_hello(new Hello);
         my_hello->setName("Cpp Hello");
         greeter_obj.attr("setHello")(my_hello);
         greeter_obj.attr("greet")();
      }
      // - Get shared_ptr from python and call method (python created)
      {
         std::cout << "Get shared_ptr from python and call method (python created)\n";
         bp::object greeter_obj = GreeterClass();
         bp::object hello_obj = HelloClass();
         hello_obj.attr("setMessage")("Hello from python");
         greeter_obj.attr("setHello")(hello_obj);
         greeter_obj.attr("greet")();
         boost::shared_ptr<Hello> the_hello = bp::extract<boost::shared_ptr<Hello> >(greeter_obj.attr("myHello")());
         the_hello->setMessage("Hello from C++");
         greeter_obj.attr("greet")();
      }
      // - Get shared_ptr from python and call method (C++ created)
      {
         std::cout << "Get shared_ptr from python and call method (C++ created).\n";
         boost::shared_ptr<Greeter> my_greeter(new Greeter());
         boost::shared_ptr<Hello> my_hello(new Hello());
         my_hello->setMessage("Hello from cpp.");
         my_greeter->setHello(my_hello);
         main_namespace["cpp_hello_obj"] = bp::object(my_hello);
         main_namespace["cpp_greeter_obj"] = bp::object(my_greeter);
         bp::object hello_obj = main_namespace["cpp_hello_obj"];
         bp::object greeter_obj = main_namespace["cpp_greeter_obj"];
         greeter_obj.attr("greet")();
         boost::shared_ptr<Hello> my_hello2 = bp::extract<boost::shared_ptr<Hello> >(greeter_obj.attr("myHello")());
         my_hello2->setName("Python Hello");
         my_greeter->greet();
      }

      // Test out the testClass stuff
      // - Let's try to do everything in C++ that we can
      PyRun_String("print 'Importing TestClass';\n"
                   "import TestClass;           \n"
                   "print dir();                \n",
                   Py_file_input,
                   main_namespace.ptr(), main_namespace.ptr());

      bp::object TestClassClass = main_namespace["TestClass"].attr("TestClass");
      bp::object PointClassClass = main_namespace["TestClass"].attr("PointClass");

      bp::object test_obj = TestClassClass("test_obj");
      test_obj.attr("printName")();
      test_obj.attr("printData")();

      bp::object point_obj = PointClassClass();
      point_obj.attr("x") = 12.0f;
      point_obj.attr("setY")(21);
      point_obj.attr("printData")();

      test_obj.attr("setPoint")(point_obj);
      test_obj.attr("printData")();

      std::cout << "\nSUCCESS: Finished everything with no exceptions or assertions. :)\n";
   }
   catch(boost::python::error_already_set)
   {
      std::cout << "Python exception:\n";
      PyErr_Print();
   }

   std::cout << "Exiting.\n" << std::endl;
   Py_Finalize();

}
